package com.service.transaction;

import com.container.app.annotations.Inject;
import com.container.app.annotations.ServiceComponent;
import com.kikidao.app.dao.DAOUtils;
import com.service.exceptions.BusinessServiceCheckedException;
import com.service.exceptions.TransactionalException;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.util.LinkedList;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: Georgiana.Scarlat
 * Date: 8/1/12
 * Time: 10:49 AM
 */

@ServiceComponent
public class TransactionManagerImpl extends TransactionManager {

    private DAOUtils daoUtils;
    private ThreadLocal<Transaction> currentTransaction = new ThreadLocal<Transaction>();
    private List<Class<? extends Throwable>> rollbackExceptions;

    public TransactionManagerImpl() {
        rollbackExceptions = new LinkedList<Class<? extends Throwable>>();
        rollbackExceptions.add(BusinessServiceCheckedException.class);
        rollbackExceptions.add(RuntimeException.class);
    }


    @SuppressWarnings("UnusedDeclaration")
    @Inject
    public void setDaoUtils(DAOUtils aFactory) {
        this.daoUtils = aFactory;
    }

    @Override
    public Transaction getTransaction(TransactionType transactionType, String name) {
        Transaction transaction = currentTransaction.get();

        //Extension: enable other propagation types for transactions

        switch (transactionType) {
            case DEFAULT:
                if (transaction == null) {
                    transaction = getNewTransaction(name);

                    System.out.println("Starting transaction " + name + "...");
                    System.out.println();

                    return transaction;
                }
                return transaction;
            default:
                break;
        }

        return null;
    }

    @Override
    public void commit(Transaction transaction) {

        Transaction trans = lookForSpecifiedTransaction(transaction);

        Connection connection = trans.getConnection();

        try {

            connection.commit();

            System.out.println("Committing transaction " + trans.getName() + "...");
            System.out.println();

        } catch (SQLException e) {
            throw new TransactionalException("Can't commit transaction. ", e);
        } finally {

            try {

                connection.setAutoCommit(trans.getPreviousAutoCommitState());

            } catch (SQLException e) {
                //noinspection ThrowFromFinallyBlock
                throw new TransactionalException("Can't reset connection state. ", e);
            }

        }

    }


    @Override
    public void rollBack(Transaction transaction) {
        Transaction trans = lookForSpecifiedTransaction(transaction);

        Connection connection = trans.getConnection();

        try {

            connection.rollback(trans.getSavepoint());

            System.out.println("Rolling back transaction " + trans.getName() + "...");
            System.out.println();

        } catch (SQLException e) {
            throw new TransactionalException("Can't rollback transaction. ", e);
        } finally {

            try {

                connection.setAutoCommit(trans.getPreviousAutoCommitState());

            } catch (SQLException e) {
                //noinspection ThrowFromFinallyBlock
                throw new TransactionalException("Can't reset connection state. ", e);
            }

        }
    }

    @Override
    public void startConnection() {

        daoUtils.getConnection();

    }

    @Override
    public void endConnection() {

        daoUtils.endConnection();
    }

    @Override
    public List<Class<? extends Throwable>> getRollbackExceptions() {

        return rollbackExceptions;
    }

    private Transaction getNewTransaction(String name) {
        try {

            Transaction transaction = new Transaction(name);

            Connection connection = daoUtils.getConnection();
            transaction.setConnection(connection);

            transaction.setPreviousAutoCommitState(connection.getAutoCommit());
            connection.setAutoCommit(false);

            Savepoint savepoint = connection.setSavepoint();
            transaction.setSavepoint(savepoint);


            currentTransaction.set(transaction);

            return transaction;

        } catch (SQLException e) {
            throw new TransactionalException("Can't initiate transaction.", e);
        }
    }

    private Transaction lookForSpecifiedTransaction(Transaction transaction) {

        //Extension: enable nested transactions

        Transaction trans = currentTransaction.get();
        currentTransaction.remove();
        if (trans == null)
            throw new TransactionalException("Trying to end nonexistent transaction. ");
        if (!trans.equals(transaction))
            throw new TransactionalException("Trying to end wrong transaction. ");
        return trans;
    }
}
